"""安居客滑块 https://www.anjuke.com/captcha-verify/?callback=shield&from=antispam"""
# -*- coding: utf-8 -*-
# @Date    : 2024/2/10
# @Author  : zouy
# @Email: 874112501@qq.com
import json
import os
import time

import ddddocr
import requests
import parsel
from loguru import logger
import execjs

DIR_PATH = os.path.dirname(os.path.abspath(__file__))


class Util:
    """工具类"""

    @staticmethod
    def save_img(url, file_name):
        """保存图片"""
        content = requests.get(url).content

        with open(os.path.join(DIR_PATH, file_name), 'wb') as f:
            f.write(content)

    @staticmethod
    def generate_track(distance):
        """生成轨迹，样本距离为 122"""
        ratio = distance / 122
        new_track = ""
        base_track = "4,26,1|4,26,136|6,26,149|7,26,164|10,26,180|13,26,188|14,26,197|18,26,204|22,26,212|25,26,221|27,26,229|32,25,237|34,25,244|37,25,254|40,25,260|42,25,270|44,25,276|46,25,285|48,25,292|50,25,301|51,25,308|52,25,316|54,25,323|54,25,333|57,25,339|58,25,349|60,25,356|62,25,364|64,25,371|65,25,380|66,25,387|66,25,396|67,25,404|69,25,412|70,25,435|71,25,445|72,25,453|73,25,461|74,25,470|74,25,476|76,25,484|78,25,500|78,25,508|79,25,515|80,25,523|81,25,532|83,25,540|86,25,549|86,25,556|88,25,563|89,25,571|90,25,580|91,25,596|93,25,612|94,25,620|94,25,628|95,25,636|97,25,644|98,25,654|99,25,660|102,25,670|103,25,676|104,25,685|105,25,691|106,25,701|108,25,724|109,26,732|110,26,739|112,26,748|113,26,756|114,26,765|115,27,771|116,27,788|118,27,843|118,28,891|118,29,980|120,29,1036|121,29,1157|122,29,1205|124,29,1245|126,29,1637|126,29,2187|"
        base_track = base_track.split("|")[:-1]
        for track in base_track:
            t = track.split(",")
            new_track += str(int(int(t[0]) * ratio)) + "," + str(t[1]) + "," + str(int(int(t[2]) * ratio)) + "|"
        return new_track

    @staticmethod
    def get_distance(puzzle_img_name, bg_img_name):
        """计算滑块缺口距离"""
        slide = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
        with open(os.path.join(DIR_PATH, puzzle_img_name), 'rb') as f:
            target_bytes = f.read()
        with open(os.path.join(DIR_PATH, bg_img_name), 'rb') as f:
            background_bytes = f.read()

        res = slide.slide_match(target_bytes, background_bytes, simple_target=True)
        print(res)
        return res['target'][0]


def captcha_verify(ss):
    """1. 获取sessionId  /captcha-verify"""
    headers = {
        "authority": "www.anjuke.com",
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    }
    url = "https://www.anjuke.com/captcha-verify/"
    params = {
        "callback": "shield",
        "from": "antispam"
    }
    response = requests.get(url, headers=headers, params=params)
    resp_text = response.text
    sel = parsel.Selector(resp_text)
    session_id = sel.css('#sessionId::attr(value)').get()
    return session_id


def get_info_tp(ss, ctx, anjuke_param, *, session_id):
    """2.获取验证码图片 /getInfoTp

        成功：{"code": 0, "message": "成功"
            "data": {
                "ntpStatus": 1,
                "ntp": "0",
                "status": 0,
                "responseId": "e9044cbf66254aee81fee7a229615dfa",  # 验证码id
                "info": "MPw1VbRfH+FbBRDD+7UA2fGBG4iwstPFagUfyLvtD6FCTYXS4z4KhwFWAdZnkpw4LH9weiwWPNtSuu+x/X6U6jomMG9EKlYnwpeiP6WU6lWZXZaM8Lp3KsjEhSLnHcw1sgereZ+zjmU5tvbG9yc/F4hq44xF9q+NZQqaMO5xDQYq3XTw3FTV4HVTkYew0m2ANLG3F+SJHvLvlV7Swf8HNY69+a5pB/2XkAXH7G1gv4cMi8+xI8QTV511WnLA0MkMQw/q8J6CiEubgJo+9ACzZENvlzPpEmAgmbaZLVQTrjTcudz/ar1By3/1UJWJB1dsYgFVKaQs5X3Ullk+b+EpT8CSnYpk50vYC2OGRd4hrYJYrfAXGgV8EgV7kMwKxb0dAUfZRNprUOhJ11gJwhgdTiClX9xDgpaotI/y1Gplh/4="
            }}

        失败：{"code": 0, "message": "校验失败" "data": { "ischange": false, "status": 1 }, }
    """
    url = "https://verifycode.58.com/captcha/getInfoTp"

    d_info = ctx.call('aesEncry', session_id, anjuke_param)
    data = {
        "sessionId": session_id,  # 请求html返回
        "showType": "embed",  # 固定
        "track": "",
        "clientType": "1",
        "clientId": "1",
        "language": "zh-CN",
        "dInfo": d_info
    }
    response = ss.post(url, data=data)
    resp_json = response.json()
    response_id = resp_json['data']['responseId']  # 验证码Id
    info = resp_json['data']['info']  # 验证码图片

    captcha_dict = ctx.call('aesDecry', session_id, info)
    logger.info(captcha_dict)

    puzzle_img_url = captcha_dict['puzzleImgUrl']  # 滑块
    bg_img_url = captcha_dict['bgImgUrl']  # 背景图

    # 保存图片
    Util.save_img(puzzle_img_url, 'puzzle_img.png')
    Util.save_img(bg_img_url, 'bg_img.png')

    # 获取缺口距离(480*270 -> 280*158)
    distance = Util.get_distance('puzzle_img.png', 'bg_img.png')

    return response_id, distance, d_info


def check_info_tp(ss, ctx, d_info, *, session_id, response_id, distance):
    """3.滑动验证码  /checkInfoTp

        成功： {"code":0,"message":"校验成功",
                "data":{"successToken":"0aa479d121d4431e9cd758a52f5aa4a9",
                    "sourceimg":"https://wos3.58cdn.com.cn/HimeDcBazHkN/captchaimg/a7f1b45d-ae24-4652-bb6e-323e4218442f.png",
                    "status":0
               }}
        失败：{"code": 0, "data": { "ischange": false, "status": 1 }, "message": "校验失败"}
     """
    url = "https://verifycode.58.com/captcha/checkInfoTp"
    x = round(distance * 280 / 480)
    track = Util.generate_track(x)
    track_info = {
        "x": x,
        "track": track,
        "p": [0, 0]
    }
    logger.info(f"轨迹：{track_info=}")
    data = ctx.call('aesEncry', session_id, json.dumps(track_info, separators=(',', ':')))
    data = {
        "sessionId": session_id,
        "responseId": response_id,  # 验证码id
        "dInfo": d_info,
        "language": "zh-CN",
        "data": data,
    }
    response = ss.post(url, data=data)
    resp_json = response.json()
    return resp_json


def init(ua):
    """初始化"""
    with open(os.path.join(DIR_PATH, "安居客.js"), encoding='utf-8') as f:
        js_code = f.read()
    ctx = execjs.compile(js_code)

    ss = requests.Session()
    ss.headers = {
        "authority": "verifycode.58.com",
        "accept": "application/json, text/javascript, */*; q=0.01",
        "accept-language": "zh-CN,zh;q=0.9",
        "cache-control": "no-cache",
        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
        "origin": "https://www.anjuke.com",
        "pragma": "no-cache",
        "referer": "https://www.anjuke.com/",
        "user-agent": ua,
    }

    anjuke_param = {
        "sdkv": "3.0.1",
        "busurl": "https://www.anjuke.com/captcha-verify/?callback=shield&from=antispam",
        "useragent": ua,
        "clienttype": "1"
    }
    return anjuke_param, ctx, ss


def main(ua):
    """入口"""
    anjuke_param, ctx, ss = init(ua)

    # 1. 获取sessionId  /captcha-verify
    session_id = captcha_verify(ss)
    logger.info(f'1.获取{session_id=}')
    # session_id = '3ed24e3a74fd4d3c8a72da63906fab8b'

    # 2.获取验证码图片 / getInfoTp
    response_id, distance, d_info = get_info_tp(ss, ctx, anjuke_param, session_id=session_id)
    logger.info(f'2.获取{response_id=}   {distance=}')

    time.sleep(3)
    # 3. 滑动验证码
    resp_json = check_info_tp(ss, ctx, d_info, session_id=session_id, response_id=response_id, distance=distance)
    if resp_json.get('data', {}).get('status') == 0:
        logger.success(f'3.成功：{resp_json}')
    else:
        logger.warning(f'3.失败：{resp_json}')


if __name__ == '__main__':
    user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
    main(user_agent)
